using System;
using System.Threading;
using System.Windows.Media;
using iLynx.Common.WPF.Imaging;

namespace iLynx.GameOfLife.Logic
{
    public abstract class LifeRenderer : IDisposable
    {
        protected readonly ReaderWriterLockSlim BufferLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
        protected int LifeWidth;
        protected int LifeHeight;
        protected byte[,] FrontBuffer;

        private readonly Color[] colours =
        {
            Color.FromArgb(255, 0, 128, 0),
            Color.FromArgb(255, 0, 255, 0),
            Color.FromArgb(255, 0, 255, 128),
            Color.FromArgb(255, 0, 128, 255),
            Color.FromArgb(255, 0, 0, 255),
            Color.FromArgb(255, 128, 0, 255),
            Color.FromArgb(255, 255, 0, 255),
            Color.FromArgb(255, 196, 0, 128),
            Color.FromArgb(255, 255, 0, 64)
        };

        protected bool IsPaused;

        public void Render(RenderContext context)
        {
            BufferLock.EnterReadLock();
            var xFactor = context.Width / (double)LifeWidth;
            var yFactor = context.Height / (double)LifeHeight;
            if (null == FrontBuffer) return;
            if (Math.Abs(xFactor - 1d) < double.Epsilon && Math.Abs(yFactor - 1d) < double.Epsilon)
                RenderOneToOne(context);
            else
                RenderScaled(context, xFactor, yFactor);
            BufferLock.ExitReadLock();

        }

        private unsafe void RenderOneToOne(RenderContext context)
        {
            var w = context.Width;
            var h = context.Height;
            var buffer = (int*)context.BackBuffer;

            for (var y = 0; y < h; ++y)
            {
                for (var x = 0; x < w; ++x)
                {
                    var value = FrontBuffer[x, y];
                    if (0 == value) continue;
                    var colour = GetColour(value);
                    buffer[x + y * w] = colour;
                }
            }
        }

        private int GetColour(byte value)
        {
            Color colour;
            var index = value - 1;
            if (index < 0 || index >= colours.Length)
                colour = Color.FromArgb(255, 255, 0, 0);
            else
                colour = colours[index];
            return colour.Intify();
        }

        private void RenderScaled(RenderContext context, double xFactor, double yFactor)
        {
            var buffer = context.BackBuffer;
            var h = context.Height;
            var w = context.Width;
            for (var y = 0; y < LifeHeight; ++y)
            {
                for (var x = 0; x < LifeWidth; ++x)
                {
                    var value = FrontBuffer[x, y];
                    if (0 == value)
                        continue;
                    var colour = GetColour(value);
                    var tX = (int)(x * xFactor);
                    var tY = (int)(y * yFactor);
                    buffer.FillQuad(w,
                        h,
                        tX, tY,
                        (int)(tX + xFactor), tY,
                        (int)(tX + xFactor), (int)(tY + yFactor),
                        tX, (int)(tY + yFactor),
                        colour);
                }
            }
        }

        public int Width
        {
            get { return LifeWidth; }
            set
            {
                if (value == LifeWidth) return;
                ChangeSettings(value, LifeHeight);
            }
        }

        public int Height
        {
            get { return LifeHeight; }
            set
            {
                if (value == LifeHeight) return;
                ChangeSettings(LifeWidth, value);
            }
        }

        protected abstract void ChangeSettings(int width, int height);

        public abstract void Clear();
        public void Pause()
        {
            BufferLock.EnterWriteLock();
            IsPaused = true;
            BufferLock.ExitWriteLock();
        }

        public void Resume()
        {
            BufferLock.EnterWriteLock();
            IsPaused = false;
            BufferLock.ExitWriteLock();
        }

        public void SeedLife(int x, int y)
        {
            BufferLock.EnterReadLock();
            try
            {
                if (!CanInsert(x, y, FrontBuffer))
                    return;
                SetCell(x, y, 1);
            }
            finally
            {
                BufferLock.ExitReadLock();
            }
        }

        public void KillLife(int x, int y)
        {
            BufferLock.EnterReadLock();
            try
            {
                if (!CanInsert(x, y, FrontBuffer))
                    return;
                SetCell(x, y, 0);
            }
            finally
            {
                BufferLock.ExitReadLock();
            }
        }

        protected static bool CanInsert(int x, int y, byte[,] buffer)
        {
            return 0 <= x && buffer.GetLength(0) > x
                &&
                   0 <= y && buffer.GetLength(1) > y;
        }

        public void InsertPattern(int x, int y, byte[,] pattern)
        {
            BufferLock.EnterReadLock();
            try
            {
                if (!CanInsert(x, y, FrontBuffer))
                    return;
                var patternX = pattern.GetLength(0);
                var patternY = pattern.GetLength(1);
                for (var dstY = y; dstY != y + patternY; ++dstY)
                {
                    for (var dstX = x; dstX != x + patternX; ++dstX)
                    {
                        if (!CanInsert(dstX, dstY, FrontBuffer)) break;
                        SetCell(dstX, dstY, pattern[dstX - x, dstY - y]);
                    }
                }
            }
            finally { BufferLock.ExitReadLock(); }
        }

        protected virtual void SetCell(int x, int y, byte state)
        {
            FrontBuffer[x, y] = state;
        }

        protected virtual void SwapFrontBufferToBack() { }

        protected virtual void SwapBackBufferToFront() { }

        public abstract bool IsRunning { get; set; }

        public abstract void SetInterval(double interval);

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public virtual void Dispose()
        {
        }

        #endregion
    }
}